#include "mqtt.h"

char			mqtt_post_msg[526];
uint32_t		mqtt_tx_len;
const uint8_t 	g_packet_heart_reply[2] = {0xc0,0x00};
unsigned char  TxBuffer[512];

//用于线程接收信息的缓冲数组和下表计数值
static   int RxCounter = 0;
static   unsigned char RxBuffer[512];


//MQTT解包函数---解析从服务器发送过来的数据
/*
服务器可能发送过来的数据报文：
CONNACK: 客户端连接MQTT服务器时，服务器发送过来的应答报文
PUBLISH:当客户端订阅了某个主题时，服务器只要收到了该主题数据，就会用发布报文转发到客户端
	(Qos为0，服务器不会发送任何ACK)
	客户端----PUBLISH消息---->服务器

PUBACK（仅限Qos为1时）:客户端发布消息时，服务器会给客户端发送一个回应，表示有收到（但这可能会导致客户端重复发送消息，导致消息重复）
	客户端----PUBLISH消息---->服务器
	客户端<----PUBACK消息-----服务器

PUBREC(仅限Qos为2时) :过程如下
PUBCOMP(仅限Qos为2时):过程如下
	客户端----PUBLISH消息---->服务器
	客户端<----PUBREC消息-----服务器
	客户端----PUBREL消息----->服务器
	客户端<----PUBCOMP消息----服务器

SUBACK:客户端

*/





void *mqtt_recvdata(void *arg)
{
	int socket_fd = (int)arg;

	while(1)
	{
		memset(RxBuffer,0,512);
		RxCounter = read(socket_fd,RxBuffer,sizeof(RxBuffer));
		if(RxCounter == -1)
		{
			perror("read err");
			continue;
		}
		
		//解包


		
	}
}







/*
函数功能：根据传入的IP及端口号，使用TCP协议连接远程的TCP服务器  内部函数
参数1：ip地址  传入ipv4地址字符串即可 如："192.168.xxx.xxx"
参数2：port 端口号 16bit无符号
返回值： 客户端套接字描述符 成功
	   -1 socket失败   -2 connect失败
*/
static int tcp_connect_server(char *ip,unsigned short port)
{
	int socketfd = socket(AF_INET,SOCK_STREAM,0);
    if(socketfd == -1)
    {
        perror("socket fail");
        return -1;
    }
    printf("socket 成功 %d\n",socketfd);

    struct sockaddr_in addr;//专属结构体类型_in TCP/UDP        _un  本地通信 
    addr.sin_family = AF_INET;//协议族 IPv4
    addr.sin_addr.s_addr = inet_addr(ip); // 服务器主机的 IP指定
    addr.sin_port = htons(port); // 服务器进程的端口号PORT 16bit
    if(-1 == connect(socketfd,(struct sockaddr *)&addr,sizeof(addr)))
    {
        perror("connect err");
        return -2;
    }
	printf("connect 成功 %d\n",socketfd);
	return socketfd;
}


//配置MQTT连接MQTT服务器
int mqtt_connect_server(char *mqttserver_ip, unsigned short mqttserver_port)
{
	int ret = 0;
	int socket_fd;
	pthread_t tid;
	//1、连接到目标MQTT-TCP服务器
	socket_fd =tcp_connect_server(mqttserver_ip,mqttserver_port);
	if(socket_fd < 0)
	{
		printf("连接MQTT服务器失败\n");
		return -1;
	}	
	printf("连接MQTT TCP服务器成功\n");


	//2、创建一个线程，专门用于接收MQTT服务器发送过来数据
	if(0 != pthread_create(&tid,NULL,mqtt_recvdata,(void *)socket_fd))
	{
		perror("线程创建失败");
		return -2;
	}
	printf("用于接收服务器信息的线程创建成功\n");

	usleep(300000);//等待一会
	
	//当前MQTT客户端连接MQTT服务器  发送CONNECT报文
	if(mqtt_connect(socket_fd,MQTT_CLIENTID, MQTT_USARNAME, MQTT_PASSWD))
	{
		printf("MQTT服务器连接失败\n");
		return -7;	
	
	}
	printf("MQTT服务器连接成功\n");
	sleep(1);		
	
	//MQTT订阅主题---这里是可选的  可以在主程序中自己决定订阅什么主题  以下是测试
	if(mqtt_subscribe_topic(socket_fd,MQTT_SUBSCRIBE_TOPIC,0,1))
	{
		printf("MQTT订阅主题失败\n");
		return -8;
	}	

	printf("MQTT订阅测试主题成功\n");

	return socket_fd;
}

//MQTT连接服务器的打包函数 提供发送的TCP客户端套接字  提供客户端ID  用户名  和 密码
//如果连接的MQTT服务器不需要用户名和密码校验，则参数3和参数4填  "" 空字符串即可
int32_t mqtt_connect(int socket_fd,char *client_id,char *user_name,char *password)
{
	uint8_t encodedByte = 0;
    uint32_t client_id_len = strlen(client_id);
    uint32_t user_name_len = strlen(user_name);
    uint32_t password_len  = strlen(password);
    uint32_t data_len;
    uint32_t cnt =2;
    uint32_t wait=0;
    mqtt_tx_len  =0;
	
    //可变报头+Payload  每个字段包含两个字节的长度标识
    data_len = 10 + (client_id_len+2) + (user_name_len+2) + (password_len+2);

    //固定报头
    //控制报文类型
    TxBuffer[mqtt_tx_len++] = 0x10;			//MQTT Message Type CONNECT
    //剩余长度(不包括固定头部)
    do
    {
        encodedByte = data_len % 128;
        data_len = data_len / 128;
        //if there are more data to encode, set the top bit of this byte
        if( data_len > 0 )
            encodedByte = encodedByte | 128;
        TxBuffer[mqtt_tx_len++] = encodedByte;
    } while( data_len > 0 );

    //可变报头
    //协议名
    TxBuffer[mqtt_tx_len++] = 0;			// Protocol Name Length MSB
    TxBuffer[mqtt_tx_len++] = 4;			// Protocol Name Length LSB
    TxBuffer[mqtt_tx_len++] = 'M';			// ASCII Code for M
    TxBuffer[mqtt_tx_len++] = 'Q';			// ASCII Code for Q
    TxBuffer[mqtt_tx_len++] = 'T';			// ASCII Code for T
    TxBuffer[mqtt_tx_len++] = 'T';			// ASCII Code for T
    //协议级别
    TxBuffer[mqtt_tx_len++] = 4;			// MQTT Protocol version = 4
    //连接标志
    TxBuffer[mqtt_tx_len++] = 0xc2;		// conn flags
    TxBuffer[mqtt_tx_len++] = 0;			// Keep-alive Time Length MSB
    TxBuffer[mqtt_tx_len++] = 60;			// Keep-alive Time Length LSB  60S心跳包

    TxBuffer[mqtt_tx_len++] = BYTE1(client_id_len);// Client ID length MSB
    TxBuffer[mqtt_tx_len++] = BYTE0(client_id_len);// Client ID length LSB
    memcpy((void *)&TxBuffer[mqtt_tx_len],client_id,client_id_len);
    mqtt_tx_len += client_id_len;

    if(user_name_len > 0)
    {
        TxBuffer[mqtt_tx_len++] = BYTE1(user_name_len);		//user_name length MSB
        TxBuffer[mqtt_tx_len++] = BYTE0(user_name_len);		//user_name length LSB
        memcpy((void *)&TxBuffer[mqtt_tx_len],user_name,user_name_len);
        mqtt_tx_len += user_name_len;
    }

    if(password_len > 0)
    {
        TxBuffer[mqtt_tx_len++] = BYTE1(password_len);			//password length MSB
        TxBuffer[mqtt_tx_len++] = BYTE0(password_len);			//password length LSB
        memcpy((void *)&TxBuffer[mqtt_tx_len],password,password_len);
        mqtt_tx_len += password_len;
    }

    while(cnt--)
    {
        memset((void *)RxBuffer,0,sizeof(RxBuffer));
		RxCounter=0;	
        //mqtt_send_bytes((void *)TxBuffer,mqtt_tx_len);
		//write发送CONNECT协议包
		write(socket_fd,TxBuffer,mqtt_tx_len);

		//等待3s时间
        wait=3000;
        while(wait--)
        {
			usleep(1000);//1ms
			//检查连接确认固定报头
            if((RxBuffer[0]==0x20) && (RxBuffer[1]==0x02))
            {
				printf("连接报文服务器发回报文字节数:%d\n",RxCounter);
				if(RxBuffer[3] == 0x00)
				{
					printf("连接已被服务器端接受，连接确认成功\n");
					//连接成功
					return 0;
				}
				else
				{
					switch(RxBuffer[3])
					{
						case 1:printf("连接已拒绝，不支持的协议版本\n");
						break;
						case 2:printf("连接已拒绝，不合格的客户端标识符\n");
						break;		
						case 3:printf("连接已拒绝，服务端不可用\n");
						break;		
						case 4:printf("连接已拒绝，无效的用户或密码\n");
						break;	
						case 5:printf("连接已拒绝，未授权\n");
						break;
						default:printf("未知响应\n");
						break;
					}
					return 0;
				} 
            }  
        }
    }
	
    return -1;
}


/**
  * @brief  MQTT订阅/取消订阅数据打包函数
  * @param  topic  		主题
  * @param  qos    		消息等级
  * @param  whether: 	1订阅 / 0取消订阅请求包
  * @retval 0：成功；
  * 		1：失败；
  */
int32_t mqtt_subscribe_topic(int socket_fd,char *topic,uint8_t qos,uint8_t whether)
{
	uint8_t encodedByte=0;
    uint32_t cnt=2;
    uint32_t wait=0;
	
    uint32_t topiclen = strlen(topic);
    uint32_t data_len = 2 + (topiclen+2) + (whether?1:0);//可变报头的长度（2字节）加上有效载荷的长度
	
	mqtt_tx_len=0;
	
    //固定报头
    //控制报文类型
    if(whether) 
		TxBuffer[mqtt_tx_len++] = 0x82; 	//消息类型和标志订阅
    else	
		TxBuffer[mqtt_tx_len++] = 0xA2; 	//取消订阅

    //剩余长度
    do
    {
        encodedByte = data_len % 128; 
        data_len 	= data_len / 128; 
        //if there are more data to encode, set the top bit of this byte
        if ( data_len > 0 )
            encodedByte = encodedByte | 128;
        TxBuffer[mqtt_tx_len++] = encodedByte;
    } while ( data_len > 0 );

    //可变报头
    TxBuffer[mqtt_tx_len++] = 0;				//消息标识符 MSB
    TxBuffer[mqtt_tx_len++] = 0x01;			//消息标识符 LSB
	
    //有效载荷
    TxBuffer[mqtt_tx_len++] = BYTE1(topiclen);	//主题长度 MSB
    TxBuffer[mqtt_tx_len++] = BYTE0(topiclen);	//主题长度 LSB
    memcpy((void *)&TxBuffer[mqtt_tx_len],topic,topiclen);
	
    mqtt_tx_len += topiclen;

    if(whether)
    {
        TxBuffer[mqtt_tx_len++] = qos;			//QoS级别
    }

    while(cnt--)
    {
		RxCounter=0;
        memset((void *)RxBuffer,0,sizeof(RxBuffer));
        //mqtt_send_bytes((void *)TxBuffer,mqtt_tx_len);
		write(socket_fd,TxBuffer,mqtt_tx_len);

        wait=3000;								//等待3s时间
        while(wait--)
        {
			usleep(1000);
			
			//检查订阅确认报头
            if(RxBuffer[0]==0x90)
            {
				printf("订阅主题确认成功\n");
				
				//获取剩余长度
				if(RxBuffer[1]==3)
				{
					printf("Success - Maximum QoS 0 is %02X\n",RxBuffer[2]);
					printf("Success - Maximum QoS 2 is %02X\n",RxBuffer[3]);		
					printf("Failure is %02X\n",RxBuffer[4]);	
				}
				//获取剩余长度
				if(RxBuffer[1]==2)
				{
					printf("Success - Maximum QoS 0 is %02X\n",RxBuffer[2]);
					printf("Success - Maximum QoS 2 is %02X\n",RxBuffer[3]);			
				}				
				
				//获取剩余长度
				if(RxBuffer[1]==1)
				{
					printf("Success - Maximum QoS 0 is %02X\n",RxBuffer[2]);		
				}
				
				//订阅成功
                return 0;
            }
        }
    }
	
    if(cnt) 
		return 0;//订阅成功
	
    return -1;
}

/**
  * @brief  MQTT发布消息 数据打包函数
  * @param  topic  		主题
  * @param  message  	消息
  * @param  qos    		消息等级
  * @retval 0：成功；
  * 		1：失败；
  */
uint32_t mqtt_publish_data(int socket_fd,char *topic, char *message, uint8_t qos)
{
//static 
	uint16_t id=0;	
    uint32_t topicLength = strlen(topic);
    uint32_t messageLength = strlen(message);

    uint32_t data_len;
	uint8_t encodedByte;

    mqtt_tx_len=0;
    //有效载荷的长度这样计算：用固定报头中的剩余长度字段的值减去可变报头的长度
    //QOS为0时没有标识符
    //数据长度             主题名   报文标识符   有效载荷
    if(qos)	data_len = (2+topicLength) + 2 + messageLength;
    else	data_len = (2+topicLength) + messageLength;

    //固定报头
    //控制报文类型
    TxBuffer[mqtt_tx_len++] = 0x30;				// MQTT Message Type PUBLISH

    //剩余长度
    do
    {
        encodedByte = data_len % 128;
        data_len = data_len / 128;
        // if there are more data to encode, set the top bit of this byte
        if ( data_len > 0 )
            encodedByte = encodedByte | 128;
        TxBuffer[mqtt_tx_len++] = encodedByte;
    } while ( data_len > 0 );

    TxBuffer[mqtt_tx_len++] = BYTE1(topicLength);	//主题长度MSB
    TxBuffer[mqtt_tx_len++] = BYTE0(topicLength);	//主题长度LSB
	
    memcpy((char *)&TxBuffer[mqtt_tx_len],topic,topicLength);	//拷贝主题
	
    mqtt_tx_len += topicLength;

    //报文标识符
    if(qos)
    {
        TxBuffer[mqtt_tx_len++] = BYTE1(id);
        TxBuffer[mqtt_tx_len++] = BYTE0(id);
        id++;
    }
	
    memcpy((char *)&TxBuffer[mqtt_tx_len],message,messageLength);
	
    mqtt_tx_len += messageLength;
	
	//mqtt_send_bytes((uint8_t *)TxBuffer,mqtt_tx_len);
	write(socket_fd,(void *)TxBuffer,mqtt_tx_len);

	//Qos等级设置的是00，因此阿里云物联网平台是没有返回响应信息的;
	return mqtt_tx_len;
}

#if 0
//设备状态上报
void mqtt_report_devices_status(void)
{
    uint8_t led1_state 	= GPIO_ReadOutputDataBit(GPIOB,GPIO_Pin_3) ? 0:1;
    uint8_t led2_state 	= GPIO_ReadOutputDataBit(GPIOB,GPIO_Pin_4) ? 0:1;
    uint8_t NO_state	= GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_5) ? 0:1;

    //把开发板相关的状态变量利用sprintf函数存放到一个数组里，再把该数组利用MQTT协议打包成消息报文
    //根据实际平台数据对应的设备信息，更改以下信息；
	sprintf(mqtt_post_msg,
		"{\"method\":\"thing.service.property.set\",\"id\":\"1597870845\",\"params\":{\
		\"NO\":%d,\
		\"led1\":%d,\
		\"led2\":%d,\
		},\"version\":\"1.0.0\"}",
		NO_state,
		led1_state,
		led2_state);

	//上报信息到平台服务器
	mqtt_publish_data(MQTT_PUBLISH_TOPIC,mqtt_post_msg,0);
}

//MQTT发送数据
void mqtt_send_bytes(uint8_t *buf,uint32_t len)
{
    esp8266_send_bytes(buf,len);
}
#endif

//发送心跳包
int32_t mqtt_send_heart(int socket_fd)
{	
	uint8_t buf[2]={0xC0,0x00};
    uint32_t cnt=2;
    uint32_t wait=0;	
	
#if 0	
	mqtt_send_bytes(buf,2);
	return 0;
#else
    while(cnt--)
    {	
		//mqtt_send_bytes(buf,2);
		write(socket_fd,buf,2);
		memset((void *)RxBuffer,0,sizeof(RxBuffer));
		RxCounter=0;	
		
		wait=3000;//等待3s时间
		
		while(wait--)
		{
			usleep(1000);

			//检查心跳响应固定报头
			if((RxBuffer[0]==0xD0) && (RxBuffer[1]==0x00)) 
			{
				printf("心跳响应确认成功，服务器在线。\n");
				return 0;
			}
		}
	}
	printf("心跳响应确认失败，服务器离线\n");
	return -1;
#endif	

}

//MQTT无条件断开
void mqtt_disconnect(int socket_fd)
{
	uint8_t buf[2]={0xe0,0x00};
	
    //mqtt_send_bytes(buf,2);
	write(socket_fd,buf,2);

	//esp8266_disconnect_server();
	close(socket_fd);
}

#if 0
/*
由于mqtt协议发布消息数据包 = 0x30+剩余长度+01+00+Topic主题名+Json内容，例如通过阿里云物联网平台发送如下			
0x30 0xE2 0x01 0x00 /thing/service/property/set{"method":"thing.service.property.set","id":"1597870845","params":{"NO":1,"led1":1,"led2":1},"version":"1.0.0"}		
传给cJSON时必须全为字符串，不能有0x00，否则遇到0x00会导致直接结束cJSON的。因此需要自行查找'{'开头的Json内容				
*/
//处理阿里云下发数据
void mqtt_msg_handle(void)
{
	uint8_t i;
	
	//等待数据接收完毕
	delay_ms(100);			
	
	//（此处可不作检索）检索接收到阿里云下发的数据是否包含有“"method"”，但是！！！如果字符串中包含\0的话，strstr不会查找到最后就会返回null；。
	//if( ! esp8266_find_str_in_rx_packet("method",5000));
	{
 		for(i=0;i<RxCounter;i++)
		{	
			//检索'{'
			if(RxBuffer[i] == '{')
			{
				//解析成功，则退出
				if( ! mqtt_cjson_parse((char *)&RxBuffer[i]))		//核心处理云端数据代码！！！数据来自串口RxBuffer
					break;
			}
		}
		//串口接收数据位置置0,即清空数组
		RxCounter = 0;			
		RxEnd=0;
	}
}

//解析MQTT下发数据
/*{
    "method":"thing.service.property.set",
    "id":"1597870845",
    "params":{
        "NO":1,
        "led1":1,
        "led2":1
    },
    "version":"1.0.0"
}
*/
uint32_t mqtt_cjson_parse(char *pbuf)
{
	cJSON *Json=NULL, *Method=NULL, *Id=NULL, *Params=NULL, *Item=NULL;

	char *psrt = pbuf;

	//解析数据包
	Json = cJSON_Parse(psrt);
	if(Json == NULL)												//检测Json数据包是否存在语法上的错误，返回NULL表示数据包无效
	{
		cJSON_Delete(Json);

		//打印数据包语法错误的位置
		printf("Error before: [%s] \r\n", cJSON_GetErrorPtr());
		return 1;
	}
	else
	{
		//匹配子对象 method
		if((Method = cJSON_GetObjectItem(Json,"method")) != NULL)
		{
			printf("---------------------------------method----------------------------\r\n");
			printf("%s: %s \r\n",Method->string,Method->valuestring);
		}
		//匹配子对象 id
		if((Id = cJSON_GetObjectItem(Json,"id")) != NULL)
		{
			printf("-----------------------------------id------------------------------\r\n");
			printf("%s: %s \r\n",Id->string,Id->valuestring);
		}

		//匹配子对象 params
		if((Params = cJSON_GetObjectItem(Json,"params")) != NULL)
		{
			printf("---------------------------------params----------------------------\r\n");

			if((Item = cJSON_GetObjectItem(Params,"NO")) != NULL)//匹配子对象2中的成员 "NO"
			{
				printf("%s: %d \r\n",Item->string,Item->valueint);

				//控制继电器
				PAout(5) = Item->valueint;
			}
			if((Item = cJSON_GetObjectItem(Params,"led1")) != NULL)//匹配子对象2中的成员 "led1"
			{
				printf("%s: %d \r\n",Item->string,Item->valueint);
				
				//控制LED3灯
				PBout(3) = !Item->valueint;
			}
			if((Item = cJSON_GetObjectItem(Params,"led2")) != NULL)//匹配子对象2中的成员 "led2"
			{
				printf("%s: %d \r\n",Item->string,Item->valueint);
				
				//控制LED4灯
				PBout(4) = !Item->valueint;
			}
		}
	}

	//释放cJSON_Parse()分配出来的内存空间
	cJSON_Delete(Json);
	Json=NULL;
	
	return 0;
}
#endif